#pragma once
#ifndef _KH_CORE_PTR_H
#define _KH_CORE_PTR_H
#include <Kharlia/Core/Common.h>

namespace kharlia { namespace core {

template <typename T> class TWeakPtr;
class CObject;

/// A pointer to a Boost.Python instance and its underlying C++ object.
/// Reference counting is implementing with a handle to the outer Python
/// instance. A pointer to the C++ object is provided for quick access.
template <typename T>
class TPtr {
    T* mPtr;
    handle<> mHandle;

    // Fast constructor for TWeakPtr
    // This allows us to avoid calling extract<T*>() again when
    // locking the weak pointer.
    template <typename H>
    TPtr(T* p, detail::borrowed<H>* h):
        mPtr(p),
        mHandle(h) {}

    // Construct from existing without type checking
    // This is used for raw pointer casting by CObject.
    TPtr(T* p, handle<> const& h):
        mPtr(p),
        mHandle(h) {}

public:
    typedef TPtr<T> ThisType;

    TPtr():
        mPtr(nullptr),
        mHandle() {}

    // p must be a new reference
    explicit TPtr(PyObject* p):
        mPtr(p == nullptr ? nullptr : extract<T*>(p)),
        mHandle(p == nullptr ? handle<>() : handle<>(p))
    {
        if (mHandle.get() == Py_None)
            Reset();
    }

    explicit TPtr(handle<> const& h):
        mPtr(h.get() == nullptr ? nullptr : extract<T*>(h.get())),
        mHandle(h.get() == nullptr ? handle<>() : h)
    {
        if (mHandle.get() == Py_None)
            Reset();
    }

    explicit TPtr(object const& o):
        mPtr(o.is_none() ? nullptr : extract<T*>(o.ptr())),
        mHandle(o.is_none() ? handle<>() : borrowed(o.ptr()))
    {
    }

    void Reset() {
        mPtr = nullptr;
        mHandle.reset();
    }

    void Reset(PyObject* p) {
        if (p == nullptr || p == Py_None)
            Reset();
        else {
            mPtr = extract<T*>(p);
            mHandle = handle<>(borrowed(p));
        }
    }

    // Operators

    T& operator*() const {
        assert(mPtr != nullptr);
        return *mPtr;
    }

    T* operator->() const {
        assert(mPtr != nullptr);
        return mPtr;
    }

    template <typename U>
    ThisType& operator=(TPtr<U> const& u) {
        mPtr = u.mPtr;
        mHandle = u.mHandle;
        return *this;
    }

    template <typename U>
    bool operator==(TPtr<U> const& other) const {
        return mHandle.get() == other.mHandle.get() ? assert(mPtr == other.mPtr), true : false;
    }

    template <typename U>
    bool operator!=(TPtr<U> const& other) const {
        return !((*this) == other);
    }

    operator bool() const {
        return mHandle.get() != nullptr;
    }

    // Accessors

    T* GetPtr() const {
        return mPtr;
    }

    handle<> const& GetHandle() const {
        return mHandle;
    }

    // Handle and pointer casts

    template <typename U>
    TPtr<U> Cast() const {
        extract<U*> ex(mHandle.get());
        if (ex.check())
            return TPtr<U>(reinterpret_cast<U*>(mPtr), mHandle);
        else
            return TPtr<U>();
    }

    template <typename U>
    TPtr<U> CastPtrDynamic() const {
        U* p = dynamic_cast<U*>(mPtr);
        if (p)
            return TPtr<U>(p, mHandle);
        else
            return TPtr<U>();
    }

    template <typename U>
    TPtr<U> CastPtrStatic() const {
        U* p = static_cast<U*>(mPtr);
        if (p)
            return TPtr<U>(p, mHandle);
        else
            return TPtr<U>();
    }

    template <typename T> friend class TPtr;
    template <typename T> friend class TWeakPtr;
    friend class CObject;
};

template <typename T, typename U>
inline TPtr<T> CastPtrDynamic(TPtr<U> const& u) {
    return u ? u.CastPtrDynamic<T>() : TPtr<T>();
}

template <typename T, typename U>
inline TPtr<T> CastPtrStatic(TPtr<U> const& u) {
    return u ? u.CastPtrStatic<T>() : TPtr<T>();
}

template <typename T>
inline void RegisterPtrConverter() {
    // A no-op since TPtr was converted into an object manager
}

// ADL functions for Boost.Python
template <class T>
inline PyObject* get_managed_object(TPtr<T> const& p, tag_t) {
    return p.GetHandle().get() ? upcast<PyObject>(p.GetHandle().get()) : Py_None;
}

template <class T>
inline T* get_pointer(TPtr<T> const& p) {
    return p.GetPtr();
}

} // namespace core

using core::TPtr;
using core::RegisterPtrConverter;

} // namespace kharlia

namespace boost { namespace python { namespace converter {

// Partial specializations for TPtr to make it an object manager and avoid the use
// of Boost.Python's registry.
using kharlia::core::TPtr;

template <typename T>
struct is_object_manager<TPtr<T>>: boost::mpl::true_ {};

template <typename T>
struct object_manager_traits<TPtr<T>>: pyobject_traits<PyObject> {
      static PyObject* adopt(PyObject* p) {
          return p;
      }
};

}}} // namespace boost::python::converter

#endif // _KH_CORE_PTR_H